home *** CD-ROM | disk | FTP | other *** search
- // -------------------------------------------------------------------------------------
- // GifImageRep.m
- // -------------------------------------------------------------------------------------
- // Permission is granted to freely redistribute this source code, and to use fragments
- // of this code in your own applications if you find them to be useful. This class,
- // along with the source code, come with no warranty of any kind, and the user assumes
- // all responsibility for its use.
- // -------------------------------------------------------------------------------------
- // Note: Much of the source in this object has been a heavy modification of various
- // public domain sources for GIF file conversion programs.
- // -------------------------------------------------------------------------------------
-
- #import <objc/objc.h>
- #import <appkit/appkit.h>
- #import <libc.h>
- #import <stdlib.h>
- #import <stdio.h>
- #import <string.h>
- #import <dpsclient/dpsclient.h>
- #import <defaults/defaults.h>
- #import "GifImageRep.h"
-
- // -------------------------------------------------------------------------------------
- // error codes & descriptions
- #define errNONE 0
- #define errTABLE_OVERFLOW 1
- #define errRASTER_SIZE 2
- #define errNO_COLORMAP 3
- #define errINVALID_FILE 4
- #define errBAD_BLOCK 5
- #define errCANT_LOAD 6
- #define errEND_OF_FILE 7
- #define errNO_IMAGE 8
- static char *errDesc[] = {
- "none",
- "code table overflow",
- "raster has the wrong size",
- "no colormap present for image",
- "invalid GIF file",
- "illegal GIF block type",
- "unable to load file (unknown reason)",
- "reached premature end-of-file"
- "no image found in file",
- 0
- };
- const char *errorDesc(int x) { return errDesc[x]; }
-
- // -------------------------------------------------------------------------------------
- @implementation GifImageRep
-
- // -------------------------------------------------------------------------------------
- // internal support methods
- // -------------------------------------------------------------------------------------
-
- /* decode a raster image */
- // Note: A 'goto'? Yuck! I know their ugly, but I just haven't remove it yet. sorry.
- - (int)_readRaster
- {
- u_char *ch, buf[260], *fill;
- u_int count, datm;
- int i, err, code, bits, thisCode;
- int codeTable[4096][2]; // LZW compression code data
- int stack[4096 * 2], *sp;
- int _datasize, _codesize, _maxsize;
- int _clear, _avail, _oldcode, _firstcode;
-
- /* initialize */
- err = errNONE;
- fill = imageRaster;
- _datasize = getc(imageStream);
- _clear = 1 << _datasize;
- _codesize = _datasize + 1;
- _avail = _clear + 2;
- _maxsize = _clear * 2;
- _oldcode = -1;
- _firstcode = -1;
- bits = 0;
- datm = 0;
- sp = stack;
-
- /* prefill table */
- for (i = 0; i < 4096; i++) {
- codeTable[i][0] = 0;
- codeTable[i][1] = (i < _clear)? i : 0;
- }
-
- /* decode raster */
- for (count = getc(imageStream); count > 0; count = getc(imageStream)) {
-
- /* read block */
- if (fread(buf, 1, count, imageStream) != count) { err = errEND_OF_FILE; break; }
-
- /* process block */
- for (ch = buf; count-- > 0; ch++) {
- datm += (u_int)(*ch) << bits;
- bits += 8;
- while (bits >= _codesize) {
-
- /* get code */
- code = datm & ((1 << _codesize) - 1);
- datm >>= _codesize;
- bits -= _codesize;
-
- /* clear table */
- if (code == _clear) {
- _codesize = _datasize + 1;
- _maxsize = _clear * 2;
- _avail = _clear + 2;
- _oldcode = -1;
- _firstcode = -1;
- sp = stack;
- for (i = 0; i < 4096; i++) {
- codeTable[i][0] = 0;
- codeTable[i][1] = (i < _clear)? i : 0;
- }
- continue;
- }
-
- /* end of decompression */
- if (code == _clear + 1) goto exitloop; /* for non-standard GIF files */
-
- /* save first code */
- if (_firstcode == -1) {
- _firstcode = _oldcode = code;
- *fill++ = code;
- continue;
- }
-
- /* save this code */
- thisCode = code;
-
- /* code check */
- if (code >= _avail) {
- *sp++ = _firstcode;
- code = _oldcode;
- }
-
- /* place codes on stack */
- while (code >= _clear) {
- *sp++ = codeTable[code][1];
- if (code == codeTable[code][0]) {
- err = errTABLE_OVERFLOW;
- goto exitloop;
- }
- code = codeTable[code][0];
- }
- *sp++ = _firstcode = codeTable[code][1];
-
- /* save in table */
- code = _avail;
- if (code < 4096) {
- codeTable[code][0] = _oldcode;
- codeTable[code][1] = _firstcode;
- _avail++;
- if ((_avail >= _maxsize) && (_maxsize < 4096)) {
- _maxsize *= 2;
- _codesize++;
- }
- }
-
- /* save old code */
- _oldcode = thisCode;
-
- /* flush stack */
- while (sp > stack) *fill++ = *--sp;
-
- }
- }
-
- }
-
- exitloop:
- if (err == errEND_OF_FILE) {
- err = errNONE;
- NXLogError("GIF premature end of file");
- }
- if (!err && (fill != imageRaster + rasterSize)) err = errRASTER_SIZE;
- return err;
-
- }
-
- /* load image raster */
- - (int)_loadImage
- {
- u_char buf[10];
- BOOL local, interleaved;
- int err, left;
-
- /* set file position */
- if (imagePos) fseek(imageStream, imagePos, SEEK_SET);
- else imagePos = ftell(imageStream);
-
- /* read image information */
- fread(buf, 1, 9, imageStream);
- left = buf[0] + (buf[1] << 8);
- rasterTop = buf[2] + (buf[3] << 8);
- pixWide = buf[4] + (buf[5] << 8);
- pixHigh = buf[6] + (buf[7] << 8);
- local = (buf[8] & 0x80)? YES : NO;
- interleaved = (buf[8] & 0x40)? YES : NO;
- rasterSize = (u_long)pixWide * (u_long)pixHigh;
-
- /* convert color map */
- if (local) {
- colorMapCount = 1 << ((buf[8] & 0x7) + 1);
- fread(colorMap, 3, colorMapCount, imageStream);
- } else
- if (!isGlobalMap) return errNO_COLORMAP;
-
- /* read image raster */
- if (!imageRaster) memset((imageRaster=(u_char*)malloc(rasterSize)), 0, rasterSize);
- if ((err = [self _readRaster]) && (err != errRASTER_SIZE)) return err;
-
- /* remove any interleave */
- if (interleaved) {
- int i, r = 0;
- u_char *temp = (u_char*)malloc(rasterSize);
- u_short *tbl = (u_short*)malloc(pixHigh * sizeof(u_short));
- for (i = rasterTop ; i < (int)(rasterTop + pixHigh); i += 8) tbl[i] = r++;
- for (i = rasterTop + 4; i < (int)(rasterTop + pixHigh); i += 8) tbl[i] = r++;
- for (i = rasterTop + 2; i < (int)(rasterTop + pixHigh); i += 4) tbl[i] = r++;
- for (i = rasterTop + 1; i < (int)(rasterTop + pixHigh); i += 2) tbl[i] = r++;
- for (i = 0, r = rasterTop; r < (int)(rasterTop + pixHigh); i++, r++)
- memcpy(&temp[i * pixWide], &imageRaster[tbl[r] * pixWide], pixWide);
- free(tbl);
- free(imageRaster);
- imageRaster = temp;
- rasterTop = 0;
- }
-
- /* return successful */
- return errNONE;
-
- }
-
- /* read GIF header info */
- - (int)_readGIFHeader
- {
- u_int screenWidth, screenHeight; // screen dimensions
- u_int background;
- u_char buf[256];
-
- /* rewind file pointer */
- rewind(imageStream);
-
- /* check gif signiture type */
- if (![[self class] validImageType:imageStream]) return errINVALID_FILE;
-
- /* read global information */
- fread(buf, 1, 7, imageStream);
- screenWidth = buf[0] + (buf[1] << 8); // not used
- screenHeight = buf[2] + (buf[3] << 8); // not used
- isGlobalMap = (buf[4] & 0x80)? YES : NO; // global color map?
- background = buf[5]; // not used
- aspectRatio = (buf[6])? ((float)buf[6] + 15.0) / 64.0 : 1.0; // aspect ratio
-
- /* load global colormap */
- if (isGlobalMap) {
- colorMapCount = 1 << ((buf[4] & 0x07) + 1);
- fread(colorMap, 3, colorMapCount, imageStream);
- }
-
- return errNONE;
- }
-
- /* initialize from file */
- - (int)_readImageAtPosition:(long int)overridePos
- {
- int err;
-
- /* read gif header info */
- if (err = [self _readGIFHeader]) return err;
-
- /* set file position override */
- if (overridePos) fseek(imageStream, overridePos, SEEK_SET);
-
- /* loop through file */
- for(;;) {
- int err;
- u_char buf[256], ch = getc(imageStream);
- switch (ch) {
- case ',' :
- if (err = [self _loadImage]) return err;
- imageNextPos = ftell(imageStream);
- return errNONE;
- case '!' : // skip extension
- for (ch=getc(imageStream); ch=getc(imageStream);)
- fread(buf, 1, ch, imageStream);
- break;
- case '\0':
- break; /* non-standard files */
- case ';' :
- return imageNextPos? errNONE: errNO_IMAGE;
- default :
- return errBAD_BLOCK;
- }
- }
-
- /* return any error */
- return errNONE;
-
- }
-
- // -------------------------------------------------------------------------------------
- // internal init method
- // -------------------------------------------------------------------------------------
-
- /* init from file stream */
- - _initFromFile:(const char*)filename atPos:(long int)overridePos
- {
- NXSize pSize;
-
- /* generic initialization */
- [self initDrawMethod:@selector(_drawImage:) inObject:self];
-
- /* local var init */
- imageRaster = (u_char*)nil;
- pixWide = 0;
- pixHigh = 0;
- rasterSize = 0L;
- isGlobalMap = NO;
- colorMapCount = 0;
- imagePos = 0L;
- imageNextPos = 0L;
-
- /* verify stream */
- imageStream = fopen(filename, "r");
- if (!imageStream) { [self free]; return (id)nil; }
-
- /* initialize */
- loadError = [self _readImageAtPosition:overridePos];
- if (loadError == errNO_IMAGE) { [self free]; return (id)nil; }
- if (!loadError && (!pixWide || !pixHigh)) loadError = errCANT_LOAD;
- if (loadError) {
- NXLogError("GIF stream error: %s", errDesc[loadError]);
- [self free];
- return (id)nil;
- }
-
- /* init size (check aspect ratio) */
- pSize.width = (float)pixWide;
- pSize.height = (float)pixHigh;
- if (aspectRatio < 1.0) pSize.width *= 1.0 / aspectRatio; else
- if (aspectRatio > 1.0) pSize.height *= aspectRatio;
-
- /* init custom image rep */
- [self setSize:&pSize];
- [self setPixelsWide:pixWide];
- [self setPixelsHigh:pixHigh];
- [self setBitsPerSample:8];
- [self setAlpha:NO];
- [self setNumColors:256];
-
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // public advertised methods
- // -------------------------------------------------------------------------------------
-
- /* return true if file is gif image (only check file extension) */
- + (BOOL)validImageFile:(const char*)filename
- {
- char *extn = rindex((char*)filename, '.');
- if (!extn) return NO;
- return strcasecmp(extn,".gif") && strcmp(extn,".g")? NO : YES;
- }
-
- /* check file type */
- + (BOOL)validImageType:(FILE*)fd
- {
- char buf[16];
- fread(buf, 1, 6, fd);
- return strncasecmp(buf, "GIF87a", 6) && strncasecmp(buf, "GIF89a", 6)? NO : YES;
- }
-
- /* read all images into a list */
- + (List*)newListFromFile:(const char*)filename
- {
- id list, _class = self;
- long int pos;
-
- /* verify file */
- if (![self validImageFile:filename]) return (id)nil;
-
- /* start loading images */
- list = [[[List alloc] initCount:1] empty];
- for (pos = 0L;;) {
- if (!(self = [[_class alloc] _initFromFile:filename atPos:pos])) break;
- [list addObject:self];
- pos = imageNextPos;
- }
-
- /* return list */
- if (![list count]) { [list free]; list = (id)nil; }
- return list;
-
- }
-
- /* init from file stream */
- - initFromFile:(const char*)filename
- {
- if (![[self class] validImageFile:filename]) { [self free]; return (id)nil; }
- return [self _initFromFile:filename atPos:0L];
- }
-
- /* free gif */
- - free
- {
- if (imageRaster) free(imageRaster);
- if (imageStream) fclose(imageStream);
- return [super free];
- }
-
- // -------------------------------------------------------------------------------------
- // custom image draw
- // -------------------------------------------------------------------------------------
-
- /* draw image */
- - _drawImage:mySelf
- {
- int r, c, bpr;
- u_char *p;
- NXRect rect = { { 0.0, 0.0 }, { 0.0, 0.0 } };
- const u_char *bitmap[5] = { 0, 0, 0, 0, 0 };
-
- /* make sure the image raster is loaded */
- if (loadError || (!imageRaster && (loadError=[self _loadImage]))) return self;
-
- /* build image bitmap */
- bitmap[0] = p = (u_char*)malloc(3L * rasterSize);
- for (r = rasterTop; r < (int)(rasterTop + pixHigh); r++) {
- u_char *s = &imageRaster[r * pixWide];
- for (c = 0; c < (int)pixWide; c++, p+=3) memcpy(p, colorMap[s[c]], 3);
- }
-
- /* render bitmap */
- rect.size = size;
- bpr = (7 + pixWide * 8 * 3) / 8;
- NXDrawBitmap(&rect, pixWide, pixHigh, 8, 3, 8*3, bpr, NO, NO, NX_RGBColorSpace, bitmap);
-
- /* free bitmap storage */
- free((char*)bitmap[0]);
-
- /* return successful */
- return self;
-
- }
-
- @end
-